﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using EmbeddedSharp.Core;
using EmbeddedSharp.Scripting;
using EmbeddedSharp.Scripting.Function;
using Zove.Runtime.Core.Expression;

namespace Zove.Runtime.Core.Zoom {

    /// <summary>
    /// Zoom class parser
    /// </summary>
    public class ZoomClassParser : IClassParser {

        protected readonly Lexer Lexer;

        protected readonly ILanguageNature LanguageNature;

        public ZoomClassParser(Lexer lexer, ILanguageNature languageNature) {
            Lexer = lexer;
            LanguageNature = languageNature;
        }

        #region IClassParser Members

        /// <summary>
        /// Parse
        /// </summary>
        /// <returns>a ZoomClass object</returns>
        public ZoomClass Parse() {
            ZoomClass c = null;

            while (Lexer.HasMoreTokens()) {
                Token token = Lexer.NextToken("class", "import");
                if (token.Value == "import") {
                    ParseImport();
                } else if (token.Value == "class") {
                    c = ParseClass();
                }
            }

            return c;
        }

        #endregion

        /// <summary>
        /// Parse the definition of a class.
        /// </summary>
        protected ZoomClass ParseClass() {
            Token token = Lexer.NextToken(TokenType.IDENTIFIER);
            var c = new ZoomClass(token.Value);
            c.Summary = Lexer.LastComment;

            token = Lexer.NextToken("{", "extends");
            if (token.Value.Equals("extends")) {
                ParseExtends(c);
            }

            token = Lexer.PeekToken();

            # region class internal

            while (token.Value != "}") {
                Token nt = Lexer.PeekToken(2);
                if (token.TokenType == TokenType.IDENTIFIER
                    && nt.TokenType == TokenType.IDENTIFIER) {
                    token = Lexer.NextToken();
                    string type = token.Value;

                    // Syntax:
                    // factor volume \m3, area \m2;
                    // factor A \radian, B \radian, C \radian;
                    // A = 60 \degree; # convert 60 degrees to PI/3 automatically
                    do {
                        token = Lexer.NextToken();
                        if (token.TokenType == TokenType.Semicolon) {
                            break;
                        }
                        if (token.TokenType == TokenType.IDENTIFIER) {
                            if (type == "const") {
                                ParseConstant(c);
                            } else {
                                var v = new ZoomVariable();
                                v.Parent = c;
                                v.Name = token.Value;
                                v.Type = type;
                                v.Summary = Lexer.LastComment;
                                c.Variables.Add(v);

                                token = Lexer.NextToken();
                                if (token.TokenType == TokenType.ForwardSlash) {
                                    if (v.Type != "factor") {
                                        throw new Exception("Only factor can declare with unit of measurement.");
                                    }

                                    // it is a unit
                                    token = Lexer.NextToken(TokenType.IDENTIFIER);
                                    string unit = token.Value;
                                    v.Unit = unit;
                                } else if (token.TokenType == TokenType.Comma) {
                                } else if (token.TokenType == TokenType.Semicolon) {
                                    break;
                                }
                            }
                        }
                    } while (Lexer.HasMoreTokens());
                } else {
                    string ln = Lexer.ReadUntil(';');
                    if (ln.Contains(":=")) {
                        // this is a function definition

                        ParseFunctionDefine(c, ln);
                        //} else if (ln.Contains("->")) {
                        //    // it is an inline function / rule declaration
                        //    // get to the end of the line

                        //    lexer.Index = offset;
                        //    while (string.IsNullOrEmpty(
                        //        (ln = lexer.NextLine()).Trim()) || ln.StartsWith("#")) {
                        //    }
                        //    ln = ln.Replace("->", ":=");
                        //    this.AddUserDefinedObject(
                        //        ln, c.Name, lexer.LineNumber,
                        //        new DefaultUserDefinedRuleFactory());
                    } else {
                        ParseEquationDefine(ln, c);
                    }
                }

                token = Lexer.PeekToken();
            }
            Lexer.NextToken(TokenType.RightBracket); // should be '}'

            return c;
        }

        /// <summary>
        /// Parse constant
        /// </summary>
        protected void ParseConstant(ZoomClass c) {
            Token token = Lexer.NextToken();

            string constantName = token.Value;
            Lexer.NextToken(TokenType.Assign);
            token = Lexer.NextToken();

            var constant = new ZoomConstant();
            constant.Name = constantName;
            constant.Value = token.Value;

            c.Constants.Add(constant);
        }

        /// <summary>
        /// Parse an equation
        /// </summary>
        /// <param name="ln">the line</param>
        /// <param name="c">the zoom class instance</param>
        protected void ParseEquationDefine(string ln, ZoomClass c) {
            string[] p = ln.Split('=');

            //for (int i = 0; i < p.Length; i++) {
            //    p[i] = p[i].Trim();
            //    p[i] = ApplyRulesInStatement(c.Name, p[i]);
            //}

            for (int i = 0; i < p.Length; i++) {
                List<string> id = Utils.GetAllIdentifiers(p[i].Trim());

                for (int j = i + 1; j < p.Length; j++) {
                    List<string> right = Utils.GetAllIdentifiers(p[j].Trim());
                    right.AddRange(id);

                    var e = new ZoomEquation();
                    e.LineNumber = Lexer.LineNumber;
                    e.Parent = c;
                    e.Left = p[i].Trim();
                    e.Right = p[j].Trim();
                    e.Equation = e.Left + "=" + e.Right;

                    foreach (string m in right) {
                        string variableName = m;
                        if (m.Contains(".")) {
                            variableName = m.Split('.')[0];
                        }
                        //object constant = GetConstant(m);
                        //if (constant != null) {
                        //    e.Equation = Utils.ReplaceString(
                        //        m, constant.ToString(), e.Equation);
                        //} else {
                        //if (c.HasVariable(variableName)) {
                            if (!HasElement(m, e)) {
                                var element = new ZoomElement {Name = m};
                                
                                e.Elements.Add(element);
                            }
                        //}
                    }

                    c.Equations.Add(e);
                }
            }
        }

        /// <summary>
        /// Parse function delcaration
        /// </summary>
        /// <param name="c">the zoom class</param>
        /// <param name="ln">the equation line</param>
        protected void ParseFunctionDefine(ZoomClass c, string ln) {
            UserDefinedFunctionNode udf = AddUserDefinedObject(
                ln, c.Name, Lexer.LineNumber, Lexer.LastComment,
                new DefaultUserDefinedFunctionFactory());
            c.AddUserDefinedFunction(udf.Name, udf);
        }

        /// <summary>
        /// Add a user defined object
        /// </summary>
        /// <param name="ln"></param>
        /// <param name="className"></param>
        /// <param name="lineNumber"></param>
        /// <param name="comment"></param>
        /// <param name="udfFactory"></param>
        /// <returns></returns>
        public UserDefinedFunctionNode AddUserDefinedObject(
            string ln, string className, int lineNumber, string comment,
            IUserDefinedFunctionFactory udfFactory) {
            var udfp
                = new UserDefinedFunctionParser(
                    LanguageNature.ExpressionParserFactory, udfFactory);
            UserDefinedFunctionNode udf = udfp.Parse(ln);
            udf.Name = className + "." + udf.Name;
            udf.ClassName = className;
            udf.LineNumber = lineNumber;
            udf.ParserFactory = LanguageNature.ExpressionParserFactory;
            udf.Comment = comment;
            udf.FunctionManager = LanguageNature.FunctionManager;

            LanguageNature.FunctionManager.Add(udf.Name, udf);

            return udf;
        }

            #endregion

        /// <summary>
        /// Check if the equation has the element
        /// </summary>
        /// <param name="name"></param>
        /// <param name="equ"></param>
        /// <returns></returns>
        public static bool HasElement(string name, ZoomEquation equ) {
            foreach (ZoomElement e in equ.Elements) {
                if (e.Name == name) {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Parse the extends section
        /// </summary>
        /// <param name="c">the zoom class object</param>
        public void ParseExtends(ZoomClass c) {
            Token token;

            do {
                token = Lexer.NextToken(TokenType.IDENTIFIER);
                c.BaseClasses.Add(token.Value);
                token = Lexer.NextToken(TokenType.LeftBracket, TokenType.Comma);
            } while (token.TokenType != TokenType.LeftBracket);
        }

        /// <summary>
        /// Parse the import instruction
        /// </summary>
        public void ParseImport() {
            Token token = Lexer.NextToken(); // import keyword
            var path = new StringBuilder();

            while (Lexer.HasMoreTokens()
                   && token.TokenType != TokenType.Semicolon) {
                path.Append(token.Value);
                token = Lexer.NextToken();
            }

            if (!Lexer.HasMoreTokens()) {
                throw new UnexpectedEndOfSourceException(string.Empty);
            }

            AddImportedPath(path.ToString());
        }

        /// <summary>
        /// Add an imported path
        /// </summary>
        /// <param name="classPath">class path</param>
        public virtual void AddImportedPath(string classPath) {
            if (!Directory.Exists(classPath)) {
                classPath = Environment.CurrentDirectory + "\\" + classPath;
                if (!Directory.Exists(classPath)) {
                    throw new DirectoryNotFoundException(classPath);
                }
            }

            LanguageNature.ClassFinder.AddClassPath(
                classPath);
        }

        /// <summary>
        /// Get a constant
        /// </summary>
        /// <param name="constantName">the constant name</param>
        /// <returns>the constant value</returns>
        public object GetConstant(string constantName) {
            if (constantName.IndexOf('.') < 0) {
                return null;
            }

            int index = constantName.IndexOf('.');
            string var = constantName.Substring(0, index);
            string constant = constantName.Substring(index + 1, constantName.Length - index - 1);
            ZoomClass zc = LanguageNature.ClassLoader.LoadByClassName(var);
            if (zc != null) {
                ZoomConstant c = zc.Constants.GetConstant(constant);
                if (c != null) {
                    return c.Value;
                }
            }

            return null;
        }
    }
}